﻿/*
 * KLF Headless Browser
 * Written by Kris Oye <kristianoye@gmail.com>
 * Copyright (C) 2011-2012.  All rights reserved.
 */
namespace KLF.Lib.Browser.DOM
{
    #region Using Statements

    using System;
    using System.Linq;
    using System.Text;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using KLF.Lib.Browser.DOM.Events;
    using KLF.Lib.Browser.DOM.Parser;
    using Jint.Native;
    using Jint;

    #endregion

    /// <summary>
    /// The Node interface is the primary datatype for the entire Document 
    /// Object Model. It represents a single node in the document tree. While
    /// all objects implementing the Node interface expose methods for dealing 
    /// with children, not all objects implementing the Node interface may 
    /// have children. For example, Text nodes may not have children, and 
    /// adding children to such nodes results in a DOMException being raised.
    /// 
    /// The attributes nodeName, nodeValue and attributes are included as a 
    /// mechanism to get at node information without casting down to the 
    /// specific derived interface. In cases where there is no obvious 
    /// mapping of these attributes for a specific nodeType (e.g., nodeValue 
    /// for an Element or attributes for a Comment), this returns null. Note 
    /// that the specialized interfaces may contain additional and more 
    /// convenient mechanisms to get and set the relevant information. 
    /// </summary>
    abstract public class Node : NodeConstants
    {
        #region Node Properties

        internal List<Node> Children { get; set; }

        public List<Element> Elements
        {
            get
            {
                List<Element> result = new List<Element>();
                foreach (Node n in Children)
                {
                    Element e = n as Element;
                    if (e != null)
                        result.Add(e);
                    result.AddRange(n.Elements);
                }
                return result;
            }
        }

        internal Document Owner { get; set; }

        internal Node Parent { get; set; }

        internal Dictionary<string, object> UserData { get; set; }

        #endregion

        #region Constructors

        internal Node()
        {
            Children = new List<Node>();
        }

        internal Node(Document owner)
            : this()
        {
            Owner = owner;
        }

        internal Node(Node n)
            : this(n.ownerDocument)
        {
        }

        #endregion

        #region DOM Level 1 Compliance

        /// <summary>
        /// Adds the node newChild to the end of the list of children of 
        /// this node. If the newChild is already in the tree, 
        /// it is first removed. 
        /// </summary>
        /// <param name="child">The node to add.</param>
        /// <returns>The node added.</returns>
        public virtual Node appendChild(Node child)
        {
            if (child is DocumentFragment)
            {
                foreach (Node c in child.Children)
                    appendChild(c);
            }
            else
            {
                if (child.Parent != null)
                {
                    child.Parent.removeChild(child);
                }
                child.Parent = this;
                child.Owner = Owner;
                Children.Add(child);
            }
            return child;
        }

        /// <summary>
        /// A NamedNodeMap containing the attributes of this node 
        /// (if it is an Element) or null otherwise. 
        /// </summary>
        virtual public NamedNodeMap attributes
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// A NodeList that contains all children of this node. If there are
        /// no children, this is a NodeList containing no nodes. The content
        /// of the returned NodeList is "live" in the sense that, for 
        /// instance, changes to the children of the node object that it was 
        /// created from are immediately reflected in the nodes returned by 
        /// the NodeList accessors; it is not a static snapshot of the 
        /// content of the node. This is true for every NodeList, including 
        /// the ones returned by the getElementsByTagName method.
        /// </summary>
        public NodeList childNodes
        {
            get
            {
                return new NodeList(Children);
            }
        }

        /// <summary>
        /// Returns a duplicate of this node, i.e., serves as a generic 
        /// copy constructor for nodes. The duplicate node has no parent
        /// (parentNode returns null.).
        /// 
        /// Cloning an Element copies all attributes and their values, 
        /// including those generated by the XML processor to represent 
        /// defaulted attributes, but this method does not copy any text 
        /// it contains unless it is a deep clone, since the text is 
        /// contained in a child Text node. Cloning any other type of node
        /// simply returns a copy of this node. 
        /// </summary>
        /// <param name="deep"></param>
        /// <returns></returns>
        abstract public Node cloneNode(bool deep);

        /// <summary>
        /// The first child of this node. If there is no such node, this returns null.
        /// </summary>
        public Node firstChild
        {
            get
            {
                return Children.Count > 0 ? Children.First() : null;
            }
        }

        /// <summary>
        /// Inserts the node newChild before the existing child node refChild. If 
        /// refChild is null, insert newChild at the end of the list of children.
        /// 
        /// If newChild is a DocumentFragment object, all of its children are 
        /// inserted, in the same order, before refChild. If the newChild is 
        /// already in the tree, it is first removed. 
        /// </summary>
        /// <param name="newChild">The new node to put in the child list.</param>
        /// <param name="refChild">The node being replaced in the list.</param>
        /// <returns>The node replaced.</returns>
        public Node insertBefore(Node newChild, Node refChild)
        {
            int index = Children.IndexOf(refChild);

            if (index > -1)
            {
                if (newChild is DocumentFragment)
                {
                    foreach (Node c in newChild.Children)
                        insertBefore(c, refChild);
                }
                else
                {
                    if (newChild.Parent != null)
                    {
                        newChild.Parent.removeChild(newChild);
                    }
                    newChild.Parent = refChild.Parent;
                    Children.Insert(index, newChild);
                    newChild.attachToDocument(Owner);
                }

                return newChild;
            }
            throw new DOMException(DOMException.NOT_FOUND_ERR);
        }

        /// <summary>
        /// Inserts a child node at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the newly added node.</param>
        /// <param name="child">The child node to insert into the document.</param>
        /// <returns>Returns the newly inserted node.</returns>
        public Node insertChild(int index, Node child)
        {
            if (child.Parent != null)
                child.Parent.removeChild(child);

            child.Parent = this;
            Children.Insert(index, child);
            child.attachToDocument(Owner);

            return child;
        }

        /// <summary>
        /// This is a convenience method to allow easy determination of whether a node has any children. 
        /// </summary>
        /// <returns>true if the node has any children, false if the node has no children.</returns>
        public bool hasChildNodes()
        {
            return (Children.Count() > 0);
        }

        /// <summary>
        /// The last child of this node. If there is no such node, this returns null.
        /// </summary>
        public Node lastChild
        {
            get
            {
                return Children.Count > 0 ? Children.Last() : null;
            }
        }

        /// <summary>
        /// The node immediately following this node. If there is no such node, this returns null.
        /// </summary>
        public Node nextSibling
        {
            get
            {
                if (Parent != null)
                {
                    int index = Parent.Children.IndexOf(this);

                    return ((index + 1) < Parent.Children.Count ? Parent.Children[index + 1] : null);
                }
                return null;
            }
        }

        /// <summary>
        /// The name of this node, depending on its type. 
        /// </summary>
        public abstract string nodeName { get; }

        /// <summary>
        /// The value of this node, depending on its type.
        /// </summary>
        public abstract string nodeValue { get; }

        /// <summary>
        /// A code representing the type of the underlying object.
        /// </summary>
        public abstract byte nodeType { get; }

        /// <summary>
        /// The parent of this node. All nodes, except Document, 
        /// DocumentFragment, and Attr may have a parent. However, if a node 
        /// has just been created and not yet added to the tree, or if it 
        /// has been removed from the tree, this is null.
        /// </summary>
        public Node parentNode { get { return Parent; } }

        /// <summary>
        /// The node immediately preceding this node. If there is no such 
        /// node, this returns null.
        /// </summary>
        public Node previousSibling
        {
            get
            {
                if (Parent != null)
                {
                    int index = Parent.Children.IndexOf(this);

                    return (index > 0 ? Parent.Children[index - 1] : null);
                }
                return null;
            }
        }

        /// <summary>
        /// Removes the child node indicated by oldChild from the 
        /// list of children, and returns it. 
        /// </summary>
        /// <param name="oldChild">The node being removed.</param>
        /// <returns>The node removed.</returns>
        public Node removeChild(Node oldChild)
        {
            if (Children.Contains(oldChild))
            {
                Children.Remove(oldChild);
                oldChild.Parent = null;
                oldChild.detachFromDocument();

                return oldChild;
            }
            throw new DOMException(DOMException.NOT_FOUND_ERR);
        }

        /// <summary>
        /// Replaces the child node oldChild with newChild in the list of 
        /// children, and returns the oldChild node. If the newChild is 
        /// already in the tree, it is first removed. 
        /// </summary>
        /// <param name="newChild">The new node to put in the child list.</param>
        /// <param name="oldChild">The node being replaced in the list.</param>
        /// <returns>The node replaced.</returns>
        public Node replaceChild(Node newChild, Node oldChild)
        {
            if (Children.Contains(oldChild))
            {
                if (newChild.Parent != null)
                {
                    newChild.Parent.removeChild(newChild);
                }
                int index = Children.IndexOf(oldChild);

                Children[index] = newChild;

                newChild.Parent = oldChild.Parent;
                newChild.attachToDocument(Owner);

                oldChild.Parent = null;
                oldChild.detachFromDocument();

                return oldChild;
            }
            throw new DOMException(DOMException.NOT_FOUND_ERR);
        }

        #endregion

        #region DOM Level 2 Compliance

        /// <summary>
        /// Returns whether this node (if it is an element) has any attributes. 
        /// </summary>
        /// <returns>Returns true if this node has any attributes, false otherwise.</returns>
        public virtual bool hasAttributes()
        {
            return false;
        }

        /// <summary>
        /// Tests whether the DOM implementation implements a specific 
        /// feature and that feature is supported by this node, as 
        /// specified in DOM Features.
        /// </summary>
        /// <param name="feature">The name of the feature to test.</param>
        /// <param name="version">This is the version number of the feature to test.</param>
        /// <returns>Returns true if the specified feature is supported on this node, false otherwise.</returns>
        public bool isSupported(string feature, string version)
        {
            if (feature.StartsWith("+"))
                feature = feature.Substring(1);

            return false;
        }

        /// <summary>
        /// Returns the local part of the qualified name of this node.
        /// 
        /// For nodes of any type other than ELEMENT_NODE and ATTRIBUTE_NODE
        /// and nodes created with a DOM Level 1 method, such as 
        /// Document.createElement(), this is always null.
        /// </summary>
        public virtual string localName
        {
            get
            {
                return null;
            }
            internal set
            {
            }
        }

        /// <summary>
        /// The namespace URI of this node, or null if it is unspecified 
        /// (see XML Namespaces).
        /// 
        /// This is not a computed value that is the result of a namespace 
        /// lookup based on an examination of the namespace declarations in
        /// scope. It is merely the namespace URI given at creation time.
        /// 
        /// For nodes of any type other than ELEMENT_NODE and ATTRIBUTE_NODE
        /// and nodes created with a DOM Level 1 method, such as 
        /// Document.createElement(), this is always null.
        /// 
        /// Note: Per the Namespaces in XML Specification [XML Namespaces] 
        /// an attribute does not inherit its namespace from the element it
        /// is attached to. If an attribute is not explicitly given a 
        /// namespace, it simply has no namespace.
        /// </summary>
        public virtual string namespaceURI
        {
            internal set
            {
            }
            get
            {
                return null;
            }
        }

        /// <summary>
        /// The Document object associated with this node. This is also the
        /// Document object used to create new nodes. When this node is a 
        /// Document this is null.
        /// </summary>
        public Document ownerDocument
        {
            get
            {
                return Owner;
            }
        }

        /// <summary>
        /// The namespace prefix of this node, or null if it is unspecified. 
        /// When it is defined to be null, setting it has no effect, 
        /// including if the node is read-only.
        /// 
        /// Note that setting this attribute, when permitted, changes the 
        /// nodeName attribute, which holds the qualified name, as well as 
        /// the tagName and name attributes of the Element and Attr 
        /// interfaces, when applicable.
        /// 
        /// Setting the prefix to null makes it unspecified, setting it to an
        /// empty string is implementation dependent.
        /// 
        /// Note also that changing the prefix of an attribute that is known 
        /// to have a default value, does not make a new attribute with the 
        /// default value and the original prefix appear, since the 
        /// namespaceURI and localName do not change.
        /// 
        /// For nodes of any type other than ELEMENT_NODE and ATTRIBUTE_NODE 
        /// and nodes created with a DOM Level 1 method, such as createElement
        /// from the Document interface, this is always null.
        /// </summary>
        public virtual string prefix
        {
            get
            {
                return null;
            }
            set
            {
            }
        }

        #endregion

        #region DOM Level 2 Events Compiance

        /// <summary>
        /// 
        /// </summary>
        internal Dictionary<string, List<object>> _events { get; set; }

        public void addEventListener(string type, object listener, bool useCapture, bool aWantsUntrusted)
        {
            type = type.ToLowerInvariant();

            if (_events == null)
                _events = new Dictionary<string, List<object>>();

            if (!_events.ContainsKey(type))
                _events[type] = new List<object>();

            if (_events.ContainsKey(type))
            {
                if (!_events[type].Contains(listener))
                    _events[type].Add(listener);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="listener"></param>
        /// <param name="useCapture"></param>
        public void addEventListener(string type, object listener, bool useCapture)
        {
            addEventListener(type, listener, useCapture, false);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="evt"></param>
        /// <returns></returns>
        public bool dispatchEvent(IEvent evt)
        {
            if (_events != null)
            {
                if (_events.ContainsKey(evt.type))
                {
                    object []listeners = _events[evt.type].ToArray();

                    foreach (object oListener in listeners)
                    {
                        if (((Event)evt).stopped)
                            continue;
                        Owner
                            .Context
                            .ExecuteEvent(evt, oListener, "on" + evt.type);
                    }
                }
            }
            if (Owner.Context != null && !((Event)evt).stopped)
            {
                Owner
                    .Context
                    .ExecuteEvent(evt, this, "on" + evt.type);
            }
            if (evt.bubbles)
            {
                if (Parent != null)
                    return Parent.dispatchEvent(evt);
            }
            return !((Event)evt).stopped;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="listener"></param>
        /// <param name="useCapture"></param>
        public void removeEventListener(string type, object listener, bool useCapture)
        {
            type = type.ToLowerInvariant();

            if (_events == null)
                return;

            if (!_events.ContainsKey(type))
                return;

            if (!_events[type].Contains(listener))
                return;

            _events[type].Remove(listener);

            //  Cleanup if possible
            if (_events[type].Count == 0)
                _events.Remove(type);

            if (_events.Count == 0)
                _events = null;
        }

        #endregion

        #region DOM Level 3 Compliance

        /// <summary>
        /// The absolute base URI of this node or null if the implementation
        /// wasn't able to obtain an absolute URI. This value is computed as
        /// described in Base URIs. However, when the Document supports the 
        /// feature "HTML" [DOM Level 2 HTML], the base URI is computed using
        /// first the value of the href attribute of the HTML BASE element 
        /// if any, and the value of the documentURI attribute from the 
        /// Document interface otherwise.
        /// </summary>
        internal StringLookup _baseURI;
        public string baseURI
        {
            internal set
            {
                _baseURI = new StringLookup(value);
            }
            get
            {
                return (_baseURI != null ? _baseURI.Value : null);
            }
        }

        /// <summary>
        /// Compares the reference node, i.e. the node on which this method
        /// is being called, with a node, i.e. the one passed as a parameter,
        /// with regard to their position in the document and according to 
        /// the document order.
        /// </summary>
        /// <param name="other">The node to compare against the reference node.</param>
        /// <returns>Returns how the node is positioned relatively to the reference node.</returns>
        public byte compareDocumentPosition(Node other)
        {
            if ((Owner == null && other.Owner == null) || Owner != other.Owner)
            {
                return DOCUMENT_POSITION_DISCONNECTED;
            }
            return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
        }

        /// <summary>
        /// This method returns a specialized object which implements the 
        /// specialized APIs of the specified feature and version, as 
        /// specified in DOM Features. The specialized object may also be
        /// obtained by using binding-specific casting methods but is not
        /// necessarily expected to, as discussed in Mixed DOM 
        /// Implementations. This method also allow the implementation to
        /// provide specialized objects which do not support the 
        /// Node interface. 
        /// </summary>
        /// <param name="feature">The name of the feature requested. Note
        /// that any plus sign "+" prepended to the name of the feature 
        /// will be ignored since it is not significant in the context 
        /// of this method.</param>
        /// <param name="version">This is the version number of the feature to test. </param>
        /// <returns>Returns an object which implements the specialized 
        /// APIs of the specified feature and version, if any, or null if
        /// there is no object which implements interfaces associated with
        /// that feature. If the DOMObject returned by this method implements
        /// the Node interface, it must delegate to the primary core Node and
        /// not return results inconsistent with the primary core Node such 
        /// as attributes, childNodes, etc.</returns>
        public object getFeature(string feature, string version)
        {
            return null;
        }

        /// <summary>
        /// Retrieves the object associated to a key on a this node. The 
        /// object must first have been set to this node by calling 
        /// setUserData with the same key.
        /// </summary>
        /// <param name="key">The key the object is associated to.</param>
        /// <returns>Returns the DOMUserData associated to the given 
        /// key on this node, or null if there was none.</returns>
        public object getUserData(string key)
        {
            return null;
        }

        /// <summary>
        /// This method checks if the specified namespaceURI is the default namespace or not. 
        /// </summary>
        /// <param name="_namespaceURI">The namespace URI to look for.</param>
        /// <returns>Returns true if the specified namespaceURI is the default namespace, false otherwise. </returns>
        public bool isDefaultNamespace(string _namespaceURI)
        {
            return ((namespaceURI == null && _namespaceURI == null) ||
                (namespaceURI != null && namespaceURI.Equals(_namespaceURI)));
        }

        /// <summary>
        /// Tests whether two nodes are equal.
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public virtual bool isEqualNode(Node other)
        {
            if (other != null && this.GetType() == other.GetType())
            {
                if (other.nodeName != nodeName) return false;
                if (other.localName != localName) return false;
                if (other.namespaceURI != namespaceURI) return false;
                if (other.prefix != prefix) return false;
                if (other.nodeValue != nodeValue) return false;

                if (other.attributes != null && attributes != null)
                {
                    if (!other.attributes.Equals(attributes)) 
                        return false;
                }
                else if (other.attributes != null || attributes != null) 
                    return false;

                if (Children != null && other.Children != null)
                {
                    if (Children.Count != other.Children.Count) 
                        return false;

                    for (int i = 0; i < Children.Count; i++)
                    {
                        if (!Children[i].isSameNode(other.Children[i]))
                            return false;
                    }
                }
                else if (Children != null || other.Children != null)
                    return false;

                return true;
            }
            return false;
        }

        /// <summary>
        /// Returns whether this node is the same node as the given one.
        /// 
        /// This method provides a way to determine whether two Node 
        /// references returned by the implementation reference the same 
        /// object. When two Node references are references to the same 
        /// object, even if through a proxy, the references may be used 
        /// completely interchangeably, such that all attributes have the 
        /// same values and calling the same DOM method on either 
        /// reference always has exactly the same effect.
        /// </summary>
        /// <param name="other">The node to test against.</param>
        /// <returns>Returns true if the nodes are the same, false otherwise.</returns>
        public bool isSameNode(Node other)
        {
            return (other != null && other.Equals(this));
        }

        /// <summary>
        /// Puts all Text nodes in the full depth of the sub-tree underneath 
        /// this Node, including attribute nodes, into a "normal" form where
        /// only structure (e.g., elements, comments, processing instructions,
        /// CDATA sections, and entity references) separates Text nodes, i.e.,
        /// there are neither adjacent Text nodes nor empty Text nodes. This
        /// can be used to ensure that the DOM view of a document is the same
        /// as if it were saved and re-loaded, and is useful when operations 
        /// (such as XPointer [XPointer] lookups) that depend on a particular 
        /// document tree structure are to be used. If the parameter 
        /// "normalize-characters" of the DOMConfiguration object attached to
        /// the Node.ownerDocument is true, this method will also fully 
        /// normalize the characters of the Text nodes. 
        /// </summary>
        public void normalize()
        {
            Node[] nodes = Children.ToArray();
            Node lastNode = null;

            foreach (Node n in nodes)
            {
                if (n.nodeType == Node.TEXT_NODE)
                {
                    Text t = n as Text;
                    if (lastNode != null && lastNode.nodeType == Node.TEXT_NODE)
                    {
                        Text p = lastNode as Text;

                        p.data = (String.IsNullOrEmpty(p.data) ? "" : p.data) +
                            (String.IsNullOrEmpty(t.data) ? "" : t.data);
                        removeChild(t);
                        continue;
                    }
                    if (t.data == null || String.IsNullOrEmpty(t.data.Trim()))
                    {
                        removeChild(t);
                        continue;
                    }
                }
                else
                {
                    n.normalize();
                }
                lastNode = n;
            }
        }

        /// <summary>
        /// Associate an object to a key on this node. The object can later be
        /// retrieved from this node by calling getUserData with the same key.
        /// </summary>
        /// <param name="key">The key to associate the object to.</param>
        /// <param name="data">The object to associate to the given key, or 
        /// null to remove any existing association to that key.</param>
        /// <param name="handler">The handler to associate to that key, or null.</param>
        /// <returns>Returns the DOMUserData previously associated to the 
        /// given key on this node, or null if there was none.</returns>
        public object setUserData(string key, object data, object handler)
        {
            object oldData = null;

            if (data != null)
            {
                if (UserData == null)
                {
                    UserData = new Dictionary<string, object>();
                }
                oldData = UserData[key];
                UserData[key] = data;
            }
            else
            {
                oldData = UserData[key];
                UserData.Remove(key);

                if (UserData.Count == 0)
                {
                    UserData = null;
                }
            }
            return oldData;
        }

        /// <summary>
        /// This attribute returns the text content of this node and its 
        /// descendants. When it is defined to be null, setting it has no 
        /// effect. On setting, any possible children this node may have 
        /// are removed and, if it the new string is not empty or null, 
        /// replaced by a single Text node containing the string this 
        /// attribute is set to.
        /// 
        /// On getting, no serialization is performed, the returned string 
        /// does not contain any markup. No whitespace normalization is 
        /// performed and the returned string does not contain the white 
        /// spaces in element content (see the attribute 
        /// Text.isElementContentWhitespace). Similarly, on setting, no 
        /// parsing is performed either, the input string is taken as pure
        /// textual content. 
        /// </summary>
        public virtual string textContent
        {
            get
            {
                StringBuilder builder = new StringBuilder();

                if (hasChildNodes())
                {
                    foreach (Node n in Children)
                    {
                        Text text = n as Text;

                        if (text != null)
                        {
                            builder.Append(text.data);
                        }
                        else
                        {
                            builder.Append(n.textContent);
                        }
                    }
                }

                return builder.ToString();
            }
            set
            {
                if (hasChildNodes())
                {
                    while (Children.Count() > 0)
                    {
                        removeChild(Children.First());
                    }
                }
                Children.Clear();
                Children.Add(new Text(Owner, value));
            }
        }

        #endregion

        #region Non-Standard DOM Methods

        /// <summary>
        /// Hack to enable IE-like event handlers.
        /// </summary>
        /// <param name="eventName"></param>
        /// <param name="callbackFunction"></param>
        public void attachEvent(string eventName, object callbackFunction)
        {
            if (eventName.StartsWith("on"))
                eventName = eventName.Substring(2);
            addEventListener(eventName, callbackFunction, false);
        }

        /// <summary>
        /// Ensures all descendants also attach to the document.
        /// </summary>
        /// <param name="owner"></param>
        internal void attachToDocument(Document owner)
        {
            Element e = this as Element;

            if (e != null && owner != null)
            {
                e.Owner = owner;
                if (!e.Owner.Elements.Contains(e))
                    e.Owner.Elements.Add(e);
            }
            foreach (Node c in Children)
            {
                c.attachToDocument(owner);
            }
        }

        /// <summary>
        /// Makes sure all descendants also detach from the document.
        /// </summary>
        internal void detachFromDocument()
        {
            Element e = this as Element;

            Owner = null;

            if (e != null && e.Owner != null)
            {
                e.Owner.Elements.Remove(e);
            }
            foreach (Node c in Children)
            {
                c.detachFromDocument();
            }
        }

        Dictionary<string, object> _stateBag;
        public object this[string str]
        {
            get
            {
                if (_stateBag != null)
                    return _stateBag[str];
                else
                    return null;
            }
            set
            {
                if (_stateBag == null)
                    _stateBag = new Dictionary<string, object>();
                _stateBag[str] = value;
            }
        }

        #endregion
    }
}
